From: Jasper St. Pierre Date: Thu, 9 Jan 2014 19:45:58 +0000 (-0500) Subject: gtkmenutracker: Update from GTK+ X-Git-Tag: archive/raspbian/50.5-1+rpi1~1^2~1013^2^2~2940 X-Git-Url: https://dgit.raspbian.org/%22http://www.example.com/cgi/%22/%22http:/www.example.com/cgi/%22?a=commitdiff_plain;h=9d683f4767142c91b03f1e5d787fcf9a4b69f178;p=gnome-shell.git gtkmenutracker: Update from GTK+ --- diff --git a/src/gtkmenutracker.c b/src/gtkmenutracker.c index 7d20b5ffd..2d803cb3b 100644 --- a/src/gtkmenutracker.c +++ b/src/gtkmenutracker.c @@ -69,12 +69,14 @@ struct _GtkMenuTracker struct _GtkMenuTrackerSection { - GMenuModel *model; + gpointer model; /* may be a GtkMenuTrackerItem or a GMenuModel */ GSList *items; gchar *action_namespace; + guint separator_label : 1; guint with_separators : 1; guint has_separator : 1; + guint is_fake : 1; gulong handler; }; @@ -82,13 +84,14 @@ struct _GtkMenuTrackerSection static GtkMenuTrackerSection * gtk_menu_tracker_section_new (GtkMenuTracker *tracker, GMenuModel *model, gboolean with_separators, + gboolean separator_label, gint offset, const gchar *action_namespace); static void gtk_menu_tracker_section_free (GtkMenuTrackerSection *section); static GtkMenuTrackerSection * gtk_menu_tracker_section_find_model (GtkMenuTrackerSection *section, - GMenuModel *model, + gpointer model, gint *offset) { GSList *item; @@ -139,8 +142,8 @@ gtk_menu_tracker_section_find_model (GtkMenuTrackerSection *section, * * could_have_separator is true in two situations: * - * - our parent section had with_separators defined and we are not the - * first section (ie: we should add a separator if we have content in + * - our parent section had with_separators defined and there are items + * before us (ie: we should add a separator if we have content in * order to divide us from the items above) * * - if we had a 'label' attribute set for this section @@ -175,11 +178,13 @@ gtk_menu_tracker_section_sync_separators (GtkMenuTrackerSection *section, { gboolean could_have_separator; - could_have_separator = (section->with_separators && i > 0) || - g_menu_model_get_item_attribute (section->model, i, "label", "s", NULL); + could_have_separator = (section->with_separators && n_items > 0) || subsection->separator_label; + /* Only pass the parent_model and parent_index in case they may be used to create the separator. */ n_items += gtk_menu_tracker_section_sync_separators (subsection, tracker, offset + n_items, - could_have_separator, section->model, i); + could_have_separator, + could_have_separator ? section->model : NULL, + could_have_separator ? i : 0); } else n_items++; @@ -187,7 +192,7 @@ gtk_menu_tracker_section_sync_separators (GtkMenuTrackerSection *section, i++; } - should_have_separator = could_have_separator && n_items != 0; + should_have_separator = !section->is_fake && could_have_separator && n_items != 0; if (should_have_separator > section->has_separator) { @@ -212,6 +217,38 @@ gtk_menu_tracker_section_sync_separators (GtkMenuTrackerSection *section, return n_items; } +static void +gtk_menu_tracker_item_visibility_changed (GtkMenuTrackerItem *item, + gboolean is_now_visible, + gpointer user_data) +{ + GtkMenuTracker *tracker = user_data; + GtkMenuTrackerSection *section; + gboolean was_visible; + gint offset = 0; + + /* remember: the item is our model */ + section = gtk_menu_tracker_section_find_model (tracker->toplevel, item, &offset); + + was_visible = section->items != NULL; + + if (is_now_visible == was_visible) + return; + + if (is_now_visible) + { + section->items = g_slist_prepend (NULL, NULL); + (* tracker->insert_func) (section->model, offset, tracker->user_data); + } + else + { + section->items = g_slist_delete_link (section->items, section->items); + (* tracker->remove_func) (offset, tracker->user_data); + } + + gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0); +} + static gint gtk_menu_tracker_section_measure (GtkMenuTrackerSection *section) { @@ -275,6 +312,10 @@ gtk_menu_tracker_add_items (GtkMenuTracker *tracker, { GtkMenuTrackerSection *subsection; gchar *action_namespace = NULL; + gboolean has_label; + + has_label = g_menu_model_get_item_attribute (model, position + n_items, + G_MENU_ATTRIBUTE_LABEL, "s", NULL); g_menu_model_get_item_attribute (model, position + n_items, G_MENU_ATTRIBUTE_ACTION_NAMESPACE, "s", &action_namespace); @@ -284,11 +325,11 @@ gtk_menu_tracker_add_items (GtkMenuTracker *tracker, gchar *namespace; namespace = g_strjoin (".", section->action_namespace, action_namespace, NULL); - subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, offset, namespace); + subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, has_label, offset, namespace); g_free (namespace); } else - subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, offset, section->action_namespace); + subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, has_label, offset, action_namespace); *change_point = g_slist_prepend (*change_point, subsection); g_free (action_namespace); @@ -300,10 +341,61 @@ gtk_menu_tracker_add_items (GtkMenuTracker *tracker, item = _gtk_menu_tracker_item_new (tracker->observable, model, position + n_items, section->action_namespace, FALSE); - (* tracker->insert_func) (item, offset, tracker->user_data); - g_object_unref (item); - *change_point = g_slist_prepend (*change_point, NULL); + /* In the case that the item may disappear we handle that by + * treating the item that we just created as being its own + * subsection. This happens as so: + * + * - the subsection is created without the possibility of + * showing a separator + * + * - the subsection will have either 0 or 1 item in it at all + * times: either the shown item or not (in the case it is + * hidden) + * + * - the created item acts as the "model" for this section + * and we use its "visiblity-changed" signal in the same + * way that we use the "items-changed" signal from a real + * GMenuModel + * + * We almost never use the '->model' stored in the section for + * anything other than lookups and for dropped the ref and + * disconnecting the signal when we destroy the menu, and we + * need to do exactly those things in this case as well. + * + * The only other thing that '->model' is used for is in the + * case that we want to show a separator, but we will never do + * that because separators are not shown for this fake section. + */ + if (_gtk_menu_tracker_item_may_disappear (item)) + { + GtkMenuTrackerSection *fake_section; + + fake_section = g_slice_new0 (GtkMenuTrackerSection); + fake_section->is_fake = TRUE; + fake_section->model = g_object_ref (item); + fake_section->handler = g_signal_connect (item, "visibility-changed", + G_CALLBACK (gtk_menu_tracker_item_visibility_changed), + tracker); + *change_point = g_slist_prepend (*change_point, fake_section); + + if (_gtk_menu_tracker_item_is_visible (item)) + { + (* tracker->insert_func) (item, offset, tracker->user_data); + fake_section->items = g_slist_prepend (NULL, NULL); + } + } + else + { + /* In the normal case, we store NULL in the linked list. + * The measurement and lookup code count NULL always as + * exactly 1: an item that will always be there. + */ + (* tracker->insert_func) (item, offset, tracker->user_data); + *change_point = g_slist_prepend (*change_point, NULL); + } + + g_object_unref (item); } } } @@ -375,6 +467,7 @@ static GtkMenuTrackerSection * gtk_menu_tracker_section_new (GtkMenuTracker *tracker, GMenuModel *model, gboolean with_separators, + gboolean separator_label, gint offset, const gchar *action_namespace) { @@ -384,6 +477,7 @@ gtk_menu_tracker_section_new (GtkMenuTracker *tracker, section->model = g_object_ref (model); section->with_separators = with_separators; section->action_namespace = g_strdup (action_namespace); + section->separator_label = separator_label; gtk_menu_tracker_add_items (tracker, section, §ion->items, offset, model, 0, g_menu_model_get_n_items (model)); section->handler = g_signal_connect (model, "items-changed", G_CALLBACK (gtk_menu_tracker_model_changed), tracker); @@ -459,7 +553,7 @@ gtk_menu_tracker_new (GtkActionObservable *observable, tracker->remove_func = remove_func; tracker->user_data = user_data; - tracker->toplevel = gtk_menu_tracker_section_new (tracker, model, with_separators, 0, action_namespace); + tracker->toplevel = gtk_menu_tracker_section_new (tracker, model, with_separators, FALSE, 0, action_namespace); gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0); return tracker; @@ -471,11 +565,20 @@ gtk_menu_tracker_new_for_item_submenu (GtkMenuTrackerItem *item, GtkMenuTrackerRemoveFunc remove_func, gpointer user_data) { - return gtk_menu_tracker_new (_gtk_menu_tracker_item_get_observable (item), - _gtk_menu_tracker_item_get_submenu (item), - TRUE, - _gtk_menu_tracker_item_get_submenu_namespace (item), - insert_func, remove_func, user_data); + GtkMenuTracker *tracker; + GMenuModel *submenu; + gchar *namespace; + + submenu = _gtk_menu_tracker_item_get_submenu (item); + namespace = _gtk_menu_tracker_item_get_submenu_namespace (item); + + tracker = gtk_menu_tracker_new (_gtk_menu_tracker_item_get_observable (item), submenu, + TRUE, namespace, insert_func, remove_func, user_data); + + g_object_unref (submenu); + g_free (namespace); + + return tracker; } /*< private > diff --git a/src/gtkmenutrackeritem.c b/src/gtkmenutrackeritem.c index 047e7b8bc..bb9a1f415 100644 --- a/src/gtkmenutrackeritem.c +++ b/src/gtkmenutrackeritem.c @@ -20,6 +20,11 @@ #include "config.h" #include "gtkmenutrackeritem.h" +#include "gtkactionmuxer.h" + +#include "gtkactionmuxer.h" + +#include /** * SECTION:gtkmenutrackeritem @@ -81,6 +86,7 @@ struct _GtkMenuTrackerItem GtkActionObservable *observable; gchar *action_namespace; + gchar *action_and_target; GMenuItem *item; GtkMenuTrackerItemRole role : 4; guint is_separator : 1; @@ -89,8 +95,14 @@ struct _GtkMenuTrackerItem guint toggled : 1; guint submenu_shown : 1; guint submenu_requested : 1; + guint hidden_when : 2; + guint is_visible : 1; }; +#define HIDDEN_NEVER 0 +#define HIDDEN_WHEN_MISSING 1 +#define HIDDEN_WHEN_DISABLED 2 + enum { PROP_0, PROP_IS_SEPARATOR, @@ -107,6 +119,7 @@ enum { }; static GParamSpec *gtk_menu_tracker_item_pspecs[N_PROPS]; +static guint gtk_menu_tracker_visibility_changed_signal; static void gtk_menu_tracker_item_init_observer_iface (GtkActionObserverInterface *iface); G_DEFINE_TYPE_WITH_CODE (GtkMenuTrackerItem, gtk_menu_tracker_item, G_TYPE_OBJECT, @@ -231,6 +244,46 @@ gtk_menu_tracker_item_class_init (GtkMenuTrackerItemClass *class) g_param_spec_boolean ("submenu-shown", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); g_object_class_install_properties (class, N_PROPS, gtk_menu_tracker_item_pspecs); + + gtk_menu_tracker_visibility_changed_signal = g_signal_new ("visibility-changed", GTK_TYPE_MENU_TRACKER_ITEM, + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, + g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, + 1, G_TYPE_BOOLEAN); +} + +/* This syncs up the visibility for the hidden-when='' case. We call it + * from the action observer functions on changes to the action group and + * on initialisation (via the action observer functions that are invoked + * at that time). + */ +static void +gtk_menu_tracker_item_update_visibility (GtkMenuTrackerItem *self) +{ + gboolean visible; + + switch (self->hidden_when) + { + case HIDDEN_NEVER: + visible = TRUE; + break; + + case HIDDEN_WHEN_MISSING: + visible = self->can_activate; + break; + + case HIDDEN_WHEN_DISABLED: + visible = self->sensitive; + break; + + default: + g_assert_not_reached (); + } + + if (visible != self->is_visible) + { + self->is_visible = visible; + g_signal_emit (self, gtk_menu_tracker_visibility_changed_signal, 0, visible); + } } static void @@ -286,6 +339,12 @@ gtk_menu_tracker_item_action_added (GtkActionObserver *observer, if (action_target) g_variant_unref (action_target); + + /* In case of hidden-when='', we want to Wait until after refreshing + * all of the properties to emit the signal that will cause the + * tracker to expose us (to prevent too much thrashing). + */ + gtk_menu_tracker_item_update_visibility (self); } static void @@ -305,6 +364,8 @@ gtk_menu_tracker_item_action_enabled_changed (GtkActionObserver *observer, self->sensitive = enabled; g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_SENSITIVE]); + + gtk_menu_tracker_item_update_visibility (self); } static void @@ -345,33 +406,52 @@ gtk_menu_tracker_item_action_removed (GtkActionObserver *observer, const gchar *action_name) { GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer); + gboolean was_sensitive, was_toggled; + GtkMenuTrackerItemRole old_role; if (!self->can_activate) return; + was_sensitive = self->sensitive; + was_toggled = self->toggled; + old_role = self->role; + + self->can_activate = FALSE; + self->sensitive = FALSE; + self->toggled = FALSE; + self->role = GTK_MENU_TRACKER_ITEM_ROLE_NORMAL; + + /* Backwards from adding: we want to remove ourselves from the menu + * -before- thrashing the properties. + */ + gtk_menu_tracker_item_update_visibility (self); + g_object_freeze_notify (G_OBJECT (self)); - if (self->sensitive) - { - self->sensitive = FALSE; - g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_SENSITIVE]); - } + if (was_sensitive) + g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_SENSITIVE]); - if (self->toggled) - { - self->toggled = FALSE; - g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_TOGGLED]); - } + if (was_toggled) + g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_TOGGLED]); - if (self->role != GTK_MENU_TRACKER_ITEM_ROLE_NORMAL) - { - self->role = GTK_MENU_TRACKER_ITEM_ROLE_NORMAL; - g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ROLE]); - } + if (old_role != GTK_MENU_TRACKER_ITEM_ROLE_NORMAL) + g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ROLE]); g_object_thaw_notify (G_OBJECT (self)); } +static void +gtk_menu_tracker_item_primary_accel_changed (GtkActionObserver *observer, + GtkActionObservable *observable, + const gchar *action_name, + const gchar *action_and_target) +{ + GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer); + + if (g_str_equal (action_and_target, self->action_and_target)) + g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ACCEL]); +} + static void gtk_menu_tracker_item_init_observer_iface (GtkActionObserverInterface *iface) { @@ -379,6 +459,7 @@ gtk_menu_tracker_item_init_observer_iface (GtkActionObserverInterface *iface) iface->action_enabled_changed = gtk_menu_tracker_item_action_enabled_changed; iface->action_state_changed = gtk_menu_tracker_item_action_state_changed; iface->action_removed = gtk_menu_tracker_item_action_removed; + iface->primary_accel_changed = gtk_menu_tracker_item_primary_accel_changed; } GtkMenuTrackerItem * @@ -390,6 +471,7 @@ _gtk_menu_tracker_item_new (GtkActionObservable *observable, { GtkMenuTrackerItem *self; const gchar *action_name; + const gchar *hidden_when; g_return_val_if_fail (GTK_IS_ACTION_OBSERVABLE (observable), NULL); g_return_val_if_fail (G_IS_MENU_MODEL (model), NULL); @@ -400,30 +482,45 @@ _gtk_menu_tracker_item_new (GtkActionObservable *observable, self->observable = g_object_ref (observable); self->is_separator = is_separator; + if (!is_separator && g_menu_item_get_attribute (self->item, "hidden-when", "&s", &hidden_when)) + { + if (g_str_equal (hidden_when, "action-disabled")) + self->hidden_when = HIDDEN_WHEN_DISABLED; + else if (g_str_equal (hidden_when, "action-missing")) + self->hidden_when = HIDDEN_WHEN_MISSING; + + /* Ignore other values -- this code may be running in context of a + * desktop shell or the like and should not spew criticals due to + * application bugs... + * + * Note: if we just set a hidden-when state, but don't get the + * action_name below then our visibility will be FALSE forever. + * That's to be expected since the action is missing... + */ + } + if (!is_separator && g_menu_item_get_attribute (self->item, "action", "&s", &action_name)) { GActionGroup *group = G_ACTION_GROUP (observable); const GVariantType *parameter_type; + GVariant *target; gboolean enabled; GVariant *state; gboolean found; - state = NULL; + target = g_menu_item_get_attribute_value (self->item, "target", NULL); - if (action_namespace) - { - gchar *full_action; + self->action_and_target = gtk_print_action_and_target (action_namespace, action_name, target); - full_action = g_strjoin (".", action_namespace, action_name, NULL); - gtk_action_observable_register_observer (self->observable, full_action, GTK_ACTION_OBSERVER (self)); - found = g_action_group_query_action (group, full_action, &enabled, ¶meter_type, NULL, NULL, &state); - g_free (full_action); - } - else - { - gtk_action_observable_register_observer (self->observable, action_name, GTK_ACTION_OBSERVER (self)); - found = g_action_group_query_action (group, action_name, &enabled, ¶meter_type, NULL, NULL, &state); - } + if (target) + g_variant_unref (target); + + action_name = strrchr (self->action_and_target, '|') + 1; + + state = NULL; + + gtk_action_observable_register_observer (self->observable, action_name, GTK_ACTION_OBSERVER (self)); + found = g_action_group_query_action (group, action_name, &enabled, ¶meter_type, NULL, NULL, &state); if (found) gtk_menu_tracker_item_action_added (GTK_ACTION_OBSERVER (self), observable, NULL, parameter_type, enabled, state); @@ -542,11 +639,18 @@ gtk_menu_tracker_item_get_toggled (GtkMenuTrackerItem *self) const gchar * gtk_menu_tracker_item_get_accel (GtkMenuTrackerItem *self) { - const gchar *accel = NULL; + const gchar *accel; + + if (!self->action_and_target) + return NULL; - g_menu_item_get_attribute (self->item, "accel", "&s", &accel); + if (g_menu_item_get_attribute (self->item, "accel", "&s", &accel)) + return accel; - return accel; + if (!GTK_IS_ACTION_MUXER (self->observable)) + return NULL; + + return gtk_action_muxer_get_primary_accel (GTK_ACTION_MUXER (self->observable), self->action_and_target); } GMenuModel * @@ -605,19 +709,10 @@ gtk_menu_tracker_item_activated (GtkMenuTrackerItem *self) if (!self->can_activate) return; - g_menu_item_get_attribute (self->item, G_MENU_ATTRIBUTE_ACTION, "&s", &action_name); + action_name = strrchr (self->action_and_target, '|') + 1; action_target = g_menu_item_get_attribute_value (self->item, G_MENU_ATTRIBUTE_TARGET, NULL); - if (self->action_namespace) - { - gchar *full_action; - - full_action = g_strjoin (".", self->action_namespace, action_name, NULL); - g_action_group_activate_action (G_ACTION_GROUP (self->observable), full_action, action_target); - g_free (full_action); - } - else - g_action_group_activate_action (G_ACTION_GROUP (self->observable), action_name, action_target); + g_action_group_activate_action (G_ACTION_GROUP (self->observable), action_name, action_target); if (action_target) g_variant_unref (action_target); @@ -784,3 +879,15 @@ gtk_menu_tracker_item_request_submenu_shown (GtkMenuTrackerItem *self, else gtk_menu_tracker_item_set_submenu_shown (self, shown); } + +gboolean +_gtk_menu_tracker_item_is_visible (GtkMenuTrackerItem *self) +{ + return self->is_visible; +} + +gboolean +_gtk_menu_tracker_item_may_disappear (GtkMenuTrackerItem *self) +{ + return self->hidden_when != HIDDEN_NEVER; +} diff --git a/src/gtkmenutrackeritem.h b/src/gtkmenutrackeritem.h index 9db30eb88..03709d605 100644 --- a/src/gtkmenutrackeritem.h +++ b/src/gtkmenutrackeritem.h @@ -72,6 +72,10 @@ GMenuModel * _gtk_menu_tracker_item_get_submenu (GtkMenu gchar * _gtk_menu_tracker_item_get_submenu_namespace (GtkMenuTrackerItem *self); +gboolean _gtk_menu_tracker_item_may_disappear (GtkMenuTrackerItem *self); + +gboolean _gtk_menu_tracker_item_is_visible (GtkMenuTrackerItem *self); + gboolean gtk_menu_tracker_item_get_should_request_show (GtkMenuTrackerItem *self); void gtk_menu_tracker_item_activated (GtkMenuTrackerItem *self);